#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <fmod.h>
#include <fmod_errors.h>
#include "guacaudio.h"
#include "shared_setup.h"
#include "chunks/GuacAudio_setup.h"

FMOD_SYSTEM* fmodSystem;

int ERRCHECK(lua_State *L, FMOD_RESULT result)
{
    if (result != FMOD_OK)
        return luaL_error(L, "[fmod] %s", result, FMOD_ErrorString(result));
    return 0;
}

int audio_destroysound(lua_State *L)
{
    FMOD_SOUND* sound = *(FMOD_SOUND**)lua_touserdata(L,1);
    FMOD_Sound_Release(sound);
    return 0;
}

int audio_soundsummary(lua_State *L)
{
    FMOD_SOUND* sound = *(FMOD_SOUND**)lua_touserdata(L,1);
    lua_pushfstring(L, "sound: %p", sound);
    return 1;
}

int audio_channelsummary(lua_State *L)
{
    FMOD_CHANNEL* channel = *(FMOD_CHANNEL**)lua_touserdata(L,1);
    lua_pushfstring(L, "channel: %p", channel);
    return 1;
}

int set_channel_hz(lua_State *L)
{
    FMOD_CHANNEL* channel = *(FMOD_CHANNEL**)lua_touserdata(L,1);
    FMOD_Channel_SetFrequency(channel, luaL_checknumber(L,2));
    return 0;
}

int get_channel_hz(lua_State *L)
{
    FMOD_CHANNEL* channel = *(FMOD_CHANNEL**)lua_touserdata(L,1);
    float hz;
    FMOD_Channel_GetFrequency(channel, &hz);
    lua_pushnumber(L, hz);
    return 1;
}

int get_channel_audibility(lua_State *L)
{
    FMOD_CHANNEL* channel = *(FMOD_CHANNEL**)lua_touserdata(L,1);
    float audibility;
    FMOD_Channel_GetAudibility(channel, &audibility);
    lua_pushnumber(L, audibility);
    return 1;
}

int get_channel_muted(lua_State *L)
{
    FMOD_CHANNEL* channel = *(FMOD_CHANNEL**)lua_touserdata(L,1);
    FMOD_BOOL mute;
    FMOD_Channel_GetMute(channel, &mute);
    lua_pushboolean(L, mute);
    return 1;
}

int set_channel_muted(lua_State *L)
{
    FMOD_CHANNEL* channel = *(FMOD_CHANNEL**)lua_touserdata(L,1);
    FMOD_Channel_SetMute(channel, lua_toboolean(L,2));
    return 0;
}

int get_channel_paused(lua_State *L)
{
    FMOD_CHANNEL* channel = *(FMOD_CHANNEL**)lua_touserdata(L,1);
    FMOD_BOOL paused;
    FMOD_Channel_GetPaused(channel, &paused);
    lua_pushboolean(L, paused);
    return 1;
}

int set_channel_paused(lua_State *L)
{
    FMOD_CHANNEL* channel = *(FMOD_CHANNEL**)lua_touserdata(L,1);
    FMOD_Channel_SetPaused(channel, lua_toboolean(L,2));
    return 0;
}

int get_channel_pan(lua_State *L)
{
    FMOD_CHANNEL* channel = *(FMOD_CHANNEL**)lua_touserdata(L,1);
    float pan;
    FMOD_Channel_GetPan(channel, &pan);
    lua_pushnumber(L, pan);
    return 1;
}

int set_channel_pan(lua_State *L)
{
    FMOD_CHANNEL* channel = *(FMOD_CHANNEL**)lua_touserdata(L,1);
    FMOD_Channel_SetPan(channel, lua_tonumber(L,2));
    return 0;
}

int get_channel_volume(lua_State *L)
{
    FMOD_CHANNEL* channel = *(FMOD_CHANNEL**)lua_touserdata(L,1);
    float volume;
    FMOD_Channel_GetVolume(channel, &volume);
    lua_pushnumber(L, volume);
    return 1;
}

int get_channel_playing(lua_State *L)
{
    FMOD_CHANNEL* channel = *(FMOD_CHANNEL**)lua_touserdata(L,1);
    FMOD_BOOL playing;
    FMOD_Channel_IsPlaying(channel, &playing);
    lua_pushboolean(L, playing);
    return 1;
}

int set_channel_volume(lua_State *L)
{
    FMOD_CHANNEL* channel = *(FMOD_CHANNEL**)lua_touserdata(L,1);
    FMOD_Channel_SetVolume(channel, lua_tonumber(L,2));
    return 0;
}

int stop_channel(lua_State *L)
{
    FMOD_CHANNEL* channel = *(FMOD_CHANNEL**)lua_touserdata(L,1);
    FMOD_Channel_Stop(channel);
    return 0;
}

int audio_update(lua_State* L)
{
    FMOD_System_Update(fmodSystem);
    return 0;
}

FMOD_RESULT F_CALLBACK OnChannelEnds(FMOD_CHANNEL *channel, FMOD_CHANNEL_CALLBACKTYPE type,
    int command, unsigned int commanddata1, unsigned int commanddata2)
{
    lua_State* L;
    FMOD_Channel_GetUserData(channel, &L);
    lua_getfield(L, LUA_REGISTRYINDEX, "channel_sound_mapping");
    int index;
    FMOD_Channel_GetIndex(channel, &index);
    lua_pushinteger(L, index);
    lua_pushnil(L);
    lua_settable(L,-3);
    lua_pop(L,1);
    return FMOD_OK;
}

static int audio_play(lua_State *L)
{
    FMOD_SOUND* sound = *(FMOD_SOUND**)lua_touserdata(L,1);
    FMOD_CHANNEL* channel;
    ERRCHECK(L, FMOD_System_PlaySound(fmodSystem, FMOD_CHANNEL_FREE, sound, lua_toboolean(L,2), &channel));
    *(FMOD_CHANNEL**)lua_newuserdata(L, sizeof(FMOD_CHANNEL*)) = channel;
    FMOD_Channel_SetUserData(channel, L);
    FMOD_Channel_SetCallback(channel, FMOD_CHANNEL_CALLBACKTYPE_END, OnChannelEnds, 0);
    int index;
    FMOD_Channel_GetIndex(channel, &index);
    lua_pushinteger(L, index);
    return 2;
}

int audio_resource(lua_State *L)
{
    luaL_checktype(L, 1, LUA_TTABLE);

    lua_getfield(L, 1, "path");
    lua_getfield(L, 1, "offset");
    lua_getfield(L, 1, "length");

    FMOD_SOUND* mySound;

    FMOD_CREATESOUNDEXINFO exInfo;
    memset(&exInfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
    exInfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);
    exInfo.fileoffset = luaL_optint(L,-2,0);
    exInfo.length = luaL_optint(L,-1,0);

    ERRCHECK(L,FMOD_System_CreateSound(fmodSystem, lua_tostring(L,-3), FMOD_SOFTWARE, &exInfo, &mySound));

    *(FMOD_SOUND**)lua_newuserdata(L, sizeof(FMOD_SOUND*)) = mySound;

    return 1;
}

int audio_data(lua_State *L)
{
    luaL_checktype(L,1,LUA_TTABLE);
    int numvalues = luaL_checkint(L,2);
    float data[numvalues];
    FMOD_System_GetWaveData(fmodSystem, data, numvalues, 0);
    int i;
    for (i = 0; i < numvalues; i++)
    {
        lua_pushnumber(L,data[i]);
        lua_rawseti(L,1,i+1);
    }
    lua_settop(L,1);
    return 1;
}

static const luaL_Reg audio_functions[] = {
    {"update", audio_update},
    {"resource", audio_resource},
    {"destroysound", audio_destroysound},
    {"soundsummary", audio_soundsummary},
    {"channelsummary", audio_channelsummary},
    {"play", audio_play},
    {"getmetatable", raw_getmetatable},
    {"setmetatable", raw_setmetatable},
    {"getfenv", raw_getfenv},
    {"setfenv", raw_setfenv},
    {"getchannelhz", get_channel_hz},
    {"setchannelhz", set_channel_hz},
    {"getchannelaudibility", get_channel_audibility},
    {"getchannelmuted", get_channel_muted},
    {"setchannelmuted", set_channel_muted},
    {"getchannelpaused", get_channel_paused},
    {"setchannelpaused", set_channel_paused},
    {"getchannelpan", get_channel_pan},
    {"setchannelpan", set_channel_pan},
    {"getchannelvolume", get_channel_volume},
    {"setchannelvolume", set_channel_volume},
    {"getchannelplaying", get_channel_playing},
    {"stopchannel", stop_channel},
    {"wavedata", audio_data},
    {NULL, NULL}
};

int luaopen_audio(lua_State *L)
{
    ERRCHECK(L,FMOD_System_Create(&fmodSystem));
    ERRCHECK(L,FMOD_System_Init(fmodSystem, 32, FMOD_INIT_NORMAL, NULL));
    FMOD_System_SetUserData(fmodSystem, L);

    lua_pushlightuserdata(L, fmodSystem);
    lua_setfield(L, LUA_REGISTRYINDEX, "FMOD_SYSTEM");

    lua_newtable(L);
    luaL_register(L, NULL, audio_functions);
    lua_pushliteral(L, "registry");
    lua_pushvalue(L, LUA_REGISTRYINDEX);
    lua_rawset(L,-3);

    luaL_loadbuffer(L, chunk_GuacAudio_setup, sizeof(chunk_GuacAudio_setup), "SYSTEM:GuacAudio_setup.lua");
    lua_insert(L,-2);
    lua_call(L,1,1);

    lua_pushvalue(L,-1);
    lua_setglobal(L,"audio");

    return 1;
}
